//! Wrap libc functions.

use core::ffi::c_void;
use core::ptr::NonNull;

/// A wrapper for `libc::malloc` and `libc::free` with RAII.
/// Covariant over `T`.
#[repr(transparent)]
pub struct MallocBox<T> {
    pointer: NonNull<T>,
}

impl<T> MallocBox<T> {
    // Should not use the value that might uninit.
    // ///  Apply for `T` size memory.
    // ///
    // /// Return `None` when failed.
    // pub fn new() -> Option<Self> {
    //     let size = size_of::<T>();
    //     let pointer = unsafe { libc::malloc(size) } as *mut T;
    //     let pointer = NonNull::new(pointer);
    //     pointer.map(|p| Self {
    //         pointer: p,
    //     })
    // }

    ///  Apply for `T` size memory and init it by `value`.
    ///
    /// Return `None` when failed.
    pub fn with_value(value: T) -> Option<Self> {
        let size = size_of::<T>();
        let pointer = unsafe { libc::malloc(size) } as *mut T;
        let pointer = NonNull::new(pointer);
        pointer.map(|p| {
            unsafe { p.write(value) };
            Self {
                pointer: p,
            }
        })
    }
}

impl<T> Drop for MallocBox<T> {
    fn drop(&mut self) {
        unsafe {
            self.pointer.drop_in_place();
            libc::free(self.pointer.as_ptr() as *mut c_void);
        }
    }
}

impl<T> AsRef<T> for MallocBox<T> {
    fn as_ref(&self) -> &T {
        unsafe { self.pointer.as_ref() }
    }
}

impl<T> AsMut<T> for MallocBox<T> {
    fn as_mut(&mut self) -> &mut T {
        unsafe { self.pointer.as_mut() }
    }
}

// because the `MallocBox<T>` is the only one which own the `T`
unsafe impl<T> Sync for MallocBox<T> where T: Sync {}
unsafe impl<T> Send for MallocBox<T> where T: Send {}

/// Get pid of current thread.
pub fn get_thread_id() -> usize {
    unsafe { libc::getpid() as usize }
}

mod tests {
    use crate::libc_wrapper::MallocBox;

    #[test]
    fn test_malloc_simple() {
        let mut p = MallocBox::<u128>::with_value(114514).unwrap();
        assert_eq!(*p.as_ref(), 114514);
        *p.as_mut() = 1919810;
        assert_eq!(*p.as_ref(), 1919810);
    }

    #[test]
    fn test_malloc_drop() {
        let mut droped = false;
        struct DropableType<'a> {
            aim_value: &'a mut bool,
        }
        impl Drop for DropableType<'_> {
            fn drop(&mut self) {
                *self.aim_value = true;
            }
        }

        let dt = DropableType {
            aim_value: &mut droped,
        };
        {
            let mut p = MallocBox::with_value(dt).unwrap();
            assert_eq!(*p.as_ref().aim_value, false);
            // dt should drop here
        }
        assert_eq!(droped, true);
    }
}
